import ApolloClient from "apollo-client";
import { ApolloLink } from "apollo-link";
import { BatchHttpLink } from "apollo-link-batch-http";
import { InMemoryCache } from "@webiny/app/apollo-client/InMemoryCache.js";
import { plugins } from "@webiny/plugins";
import { ApolloDynamicLink } from "@webiny/app/plugins/ApolloDynamicLink.js";
import { ApolloCacheObjectIdPlugin } from "@webiny/app/plugins/ApolloCacheObjectIdPlugin.js";
import { IntrospectionFragmentMatcher } from "@webiny/app/apollo-client/IntrospectionFragmentMatcher.js";

export interface CreateApolloClientParams {
    uri: string;
    batching?: Pick<BatchHttpLink.Options, "batchMax" | "batchInterval" | "batchKey">;
}

export const createApolloClient = ({ uri, batching }: CreateApolloClientParams) => {
    const fragmentMatcher = new IntrospectionFragmentMatcher({
        introspectionQueryResultData: {
            __schema: {
                types: []
            }
        }
    });

    return new ApolloClient({
        link: ApolloLink.from([
            /**
             * This will process links from plugins on every request.
             */
            new ApolloDynamicLink(),
            /**
             * This batches requests made to the API to pack multiple requests into a single HTTP request.
             * `credentials: "include"` is necessary to attach cookies to requests.
             */
            new BatchHttpLink({ uri, credentials: "include", ...batching })
        ]),
        cache: new InMemoryCache({
            addTypename: true,
            fragmentMatcher,
            dataIdFromObject: obj => {
                /**
                 * Since every data type coming from API can have a different data structure,
                 * we cannot rely on having an `id` field.
                 */
                const getters = plugins.byType<ApolloCacheObjectIdPlugin>(
                    ApolloCacheObjectIdPlugin.type
                );

                for (let i = 0; i < getters.length; i++) {
                    const id = getters[i].getObjectId(obj);
                    if (typeof id !== "undefined") {
                        return id;
                    }
                }

                /**
                 * As a fallback, try getting object's `id`.
                 */
                return obj.id || null;
            }
        })
    });
};
